#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include "OBLFair_Table.h"
#include "sthread.h"


int printHungerQueue(OBLFairTable *t){
  Node *temp = t->front;
  printf("Hunger Queue: ");
  while(temp != NULL){
    printf("%d ", temp->phil);
    temp = temp->next;
  }
  printf("\n\n");
  return 0;
}

/*
 * return 1 if all plates have been dirtied, 0 otherwise
 */
int allPlatesDirty(OBLFairTable *t)
{
  
  // if anyone hasn't started eating, return 0
  int k = 0;
  while (k != t->N){
    if(!(t->hasEaten[k])){
      return 0;
    }
    ++k;
  }


  // if everyone is done eating, then they should all be in the hunger queue
  k = 0;
  Node* temp = t->front;
  while(temp != NULL){
    ++k;
    temp = temp->next;
  }

  return k == t->N;
}


/*
 * return 1 if it is time for servant to clean, 0 otherwise
 */
int timeToClean(OBLFairTable *t)
{
  if(t->bowl_status == F_SERVANT){
    return 0;
  }

  if(!allPlatesDirty(t)){
    return 0;
  }

  return 1;
  
/*  int k;*/
/*  // if everyone is done eating, then they should all be in the hunger queue*/
/*  k = 0;*/
/*  Node* temp = t->front;*/
/*  while(temp != NULL){*/
/*    ++k;*/
/*    temp = temp->next;*/
/*  }*/

/*  return k == t->N;*/
}


/*
 * return 1 if philosopher is not among the first k elts in hunger-queue, 0 otherwise
 * Note: k = number of servings in bowl
 */
int notHungryEnough(OBLFairTable *t, int philosopher)
{
  if (t->bowl >= t->N){
    return 0;
  }

  int k;
  Node* temp = t->front;
  for(k = 0; k != t->bowl && NULL != temp; ++k) {
    if(temp->phil == philosopher){
      return 0;
    }
    temp = temp->next;
  }
  return 1;
}

/*
 * return 1 if either of the chopsticks is unavailable, 0 otherwise
 */
int chopsticksUnavailable(OBLFairTable *t, int philosopher)
{
  return (t->chopsticks[philosopher] || t->chopsticks[(philosopher-1)>=0?(philosopher-1):(t->N-1)]);
}

void grabChopsticks(OBLFairTable *t, int philosopher)
{
  // assert chopsticks are not held
  assert(!(t->chopsticks[philosopher]));
  assert(!(t->chopsticks[(philosopher-1)>=0?(philosopher-1):(t->N-1)]));
  
  t->chopsticks[philosopher] = 1;
  t->chopsticks[(philosopher-1)>=0?(philosopher-1):(t->N-1)] = 1;
}


void releaseChopsticks(OBLFairTable *t, int philosopher)
{
  // assert chopsticks are held
  assert(t->chopsticks[philosopher]);
  assert(t->chopsticks[(philosopher-1)>=0?(philosopher-1):(t->N-1)]);
  
  t->chopsticks[philosopher] = 0;
  t->chopsticks[(philosopher-1)>=0?(philosopher-1):(t->N-1)] = 0;
}


void OBLF_init(OBLFairTable *t, int nPlates)
{  
/*  int i;*/
  // number of philosophers,chopsticks
  t->N = nPlates;
  
  // initialize chopsticks, plates, hasEaten arrays (all entries initially 0)
  t->chopsticks = calloc(nPlates, sizeof(int));
  t->plates = calloc(nPlates, sizeof(int));               // each philosopher initially holds his own plate
  t->hasEaten = calloc(nPlates, sizeof(int));

  // bowl initially empty and at table
  t->bowl = 0;
  t->bowl_status = TABLE;

  // set up hunger (priority) queue
/*  Node* n;*/
/*  Node* p = NULL;*/
/*  for(i=0;i<nPlates;i++) {*/
/*     n = (Node *)malloc(sizeof(Node));*/
/*     if(0==i){*/
/*        t->front = n;*/
/*        t->front->prev = NULL;*/
/*     }*/
/*     n->phil = i;*/
/*     */
/*     if(p != NULL){*/
/*       p->next = n;*/
/*       n->prev = p;*/
/*     }*/

/*     p = n;*/
/*  }*/
/*  n->next = NULL;*/
  t->front = NULL;
  
  // initialize bowl lock, master mutex, cond vars
  smutex_init(&(t->mutex));                                         // initialize the mutex
  scond_init(&(t->cond), &(t->mutex));                              // initialize the condition variables, link to the mutex
  scond_init(&(t->cond2), &(t->mutex));

  t->cleaning_mode = 0;
}


void OBLF_startEating(OBLFairTable *t, int philosopher)
{
  // grab the master mutex
  smutex_lock(&(t->mutex)); 

  // malloc and enqueue new node
  Node* n = (Node *)malloc(sizeof(Node));
  n->phil = philosopher;
  Node* temp = t->front;
  if(NULL == temp) {
    t->front = n;
    t->front->next = NULL;
    t->front->prev = NULL;
  } else {
    while(NULL != temp->next) {
      temp = temp->next;
    }
    temp->next = n;
    n->prev    = temp;
    n->next    = NULL;
  }
  //done enqueueing
  
  if (t->bowl == 0){
    scond_broadcast(&(t->cond2),&(t->mutex));
  }

  while(t->bowl == 0                                  ||
        t->bowl_status != TABLE                       ||
        notHungryEnough(t, philosopher)               ||
        t->plates[philosopher] == 1                   || // true if plate is held by cleaning servant
        chopsticksUnavailable(t, philosopher)
  ){
      scond_wait(&(t->cond),&(t->mutex)); // wait
  }

  assert(t->bowl_status == TABLE);
  assert(t->bowl > 0);
  assert(t->plates[philosopher] == 0);

  // grab the chopsticks
  grabChopsticks(t, philosopher);

  // remove serving and dequeue philosopher from hunger queue
  t->bowl--;
/*  Node* temp = t->front;*/
  temp = t->front;
  if(NULL != t->front) {
    if(t->front->phil == philosopher) {
      t->front = t->front->next;
      if (t->front != NULL){
        t->front->prev = NULL;
      }
      free(temp);
    } else {
      while(NULL != temp && temp->phil != philosopher) {
        temp = temp->next;
      }
      assert(NULL != temp);
      temp->prev->next = temp->next;
      if(NULL != temp->next){
        temp->next->prev = temp->prev;
      }
      free(temp);
    }
  }
  //done dequeueing

  // philosopher has dirtied his plate...
  t->hasEaten[philosopher] = 1;

  // broadcast to the servants in case now time to clean/refill
  scond_broadcast(&(t->cond2),&(t->mutex));

  // return the big lock
  smutex_unlock(&(t->mutex));
}


void OBLF_doneEating(OBLFairTable *t, int philosopher)
{
  // grab the lock
  smutex_lock(&(t->mutex));

  if (t->bowl == 0){
    scond_broadcast(&(t->cond2),&(t->mutex));
  }
  
  // release the chopsticks
  releaseChopsticks(t,philosopher);

  // malloc and enqueue new node
/*  Node* n = (Node *)malloc(sizeof(Node));*/
/*  n->phil = philosopher;*/
/*  Node* temp = t->front;*/
/*  if(NULL == temp) {*/
/*    t->front = n;*/
/*    t->front->next = NULL;*/
/*    t->front->prev = NULL;*/
/*  } else {*/
/*    while(NULL != temp->next) {*/
/*      temp = temp->next;*/
/*    }*/
/*    temp->next = n;*/
/*    n->prev    = temp;*/
/*    n->next    = NULL;*/
/*  }*/

  // we have released some chopsticks, so signal the next philosopher or servant
  //scond_signal(&(t->cond),&(t->mutex));
  scond_broadcast(&(t->cond),&(t->mutex));

  // broadcast to the servants in case now time to clean/refill
  scond_broadcast(&(t->cond2),&(t->mutex));

  // return the lock
  smutex_unlock(&(t->mutex));
}



void OBLF_takeEmptyBowl(OBLFairTable *t)
{
  // grab the lock
  smutex_lock(&(t->mutex));
  
  assert(t->bowl_status != F_SERVANT);

  while(t->bowl != 0                                  ||
        t->bowl_status == C_SERVANT                   ||
        t->cleaning_mode == 1                            ||
        timeToClean(t)
  ){
     scond_signal(&(t->cond2),&(t->mutex));
     scond_wait(&(t->cond2),&(t->mutex)); // wait
  }

  assert(!(timeToClean(t)));
  assert(t->bowl_status == TABLE);
  assert(t->bowl == 0);
  t->bowl_status = F_SERVANT;

  // return the lock
  smutex_unlock(&(t->mutex));
}

void OBLF_returnRefilledBowl(OBLFairTable *t, int servingCount)
{
  // grab the lock
  smutex_lock(&(t->mutex));
  
  assert(servingCount > 0);
  assert(t->bowl_status == F_SERVANT);
  
  t->bowl = servingCount;
  t->bowl_status = TABLE;


  // broadcast to philosophers that bowl is at table and no longer empty
  scond_broadcast(&(t->cond),&(t->mutex));

  // remove cleaning servant from waiting list in case he is on it
  scond_signal(&(t->cond2),&(t->mutex));
  
  // return the lock
  smutex_unlock(&(t->mutex));
}


void OBLF_waitUntilAllPlatesDirty(OBLFairTable *t)
{
  // grab the lock
  smutex_lock(&(t->mutex));

  while (!allPlatesDirty(t)){
      scond_signal(&(t->cond2),&(t->mutex));
      scond_wait(&(t->cond2),&(t->mutex)); // wait
  }

  // return the lock
  smutex_unlock(&(t->mutex));
}


void OBLF_gatherSticks(OBLFairTable *t)
{
  // grab the lock
  smutex_lock(&(t->mutex));

  while(t->bowl != 0                                  ||
        !timeToClean(t)
  ){
     scond_signal(&(t->cond2),&(t->mutex));
     scond_wait(&(t->cond2),&(t->mutex)); // wait
  }

  t->cleaning_mode = 1;
  
  /*
   * assert philosophers are not holding any sticks, 
   * and have cleaning servant grab each stick
   */
  int k;
  for(k = 0; k != t->N; ++k){
    assert(t->chopsticks[k] == 0);
    t->chopsticks[k] = 1;
  }

  // return the lock
  smutex_unlock(&(t->mutex));
}


void OBLF_gatherBowl(OBLFairTable *t)
{
  // grab the lock
  smutex_lock(&(t->mutex));

  while(t->bowl != 0                                  ||
        !timeToClean(t)
  ){
     scond_signal(&(t->cond2),&(t->mutex));
     scond_wait(&(t->cond2),&(t->mutex)); // wait
  }
/*printf("gathering bowl\n");*/
  
  t->cleaning_mode = 1;

  assert(t->bowl == 0);
  assert(t->bowl_status != F_SERVANT);

  t->bowl_status = C_SERVANT;
  assert(t->bowl == 0);
  // return the lock
  smutex_unlock(&(t->mutex));
}


void OBLF_gatherPlates(OBLFairTable *t)
{
  // grab the lock
  smutex_lock(&(t->mutex));

  while(t->bowl != 0                                  ||
        !timeToClean(t)
  ){
     scond_signal(&(t->cond2),&(t->mutex));
     scond_wait(&(t->cond2),&(t->mutex)); // wait
  }
/*printf("gathering plate\n");*/

  t->cleaning_mode = 1;
  
  /*
   * assert philosophers are each holding their own plate, 
   * and have cleaning servant grab each plate
   */
  int k;
  for(k = 0; k != t->N; ++k){
    assert(!(t->plates[k]));
    t->plates[k] = 1;
  }

  // return the lock
  smutex_unlock(&(t->mutex));
}


void OBLF_returnSticks(OBLFairTable *t)
{
  // grab the lock
  smutex_lock(&(t->mutex));

  // return each stick
  int k;
  for(k = 0; k != t->N; ++k){
    assert(t->chopsticks[k]);
    t->chopsticks[k] = 0;
  }
  
  // we have returned all chopsticks, so broadcast this to philosophers
  scond_broadcast(&(t->cond),&(t->mutex));
  
  // signal to filling servant that chopsticks are returned
  scond_signal(&(t->cond2),&(t->mutex));

  // return the lock
  smutex_unlock(&(t->mutex));
}


void OBLF_returnBowl(OBLFairTable *t)
{
  // grab the lock
  smutex_lock(&(t->mutex));

  assert(t->bowl == 0);
  assert(t->bowl_status == C_SERVANT);

  t->bowl_status = TABLE;
  t->cleaning_mode = 0;
  
  // we have returned the bowl, so broadcast this to philosophers
  scond_broadcast(&(t->cond),&(t->mutex));
  
  // signal to filling servant that bowl is back at table
  scond_signal(&(t->cond2),&(t->mutex));

  // return the lock
  smutex_unlock(&(t->mutex));
}


void OBLF_returnPlates(OBLFairTable *t)
{
  // grab the lock
  smutex_lock(&(t->mutex));

  /*
   * assert cleaning servant is holding each plate, 
   * and have him give them back to the philosophers
   */
  int k;
  for(k = 0; k != t->N; ++k){
    assert(t->plates[k]);
    t->plates[k] = 0;
    t->hasEaten[k] = 0;
  }
  
  // we have returned all plates, so broadcast this to philosophers
  scond_broadcast(&(t->cond),&(t->mutex));
  
  // signal to filling servant that plates are returned
  scond_signal(&(t->cond2),&(t->mutex));

  // return the lock
  smutex_unlock(&(t->mutex));
}

